﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Sockets;
using System.Text;
using Duo1JHttpServer.Framework.Enum;
using Duo1JHttpServer.Framework.Model;
using Duo1JHttpServer.Framework.Utils;

namespace Duo1JHttpServer.Framework.Server
{
    /// <summary>
    /// http前处理器
    /// </summary>
    internal sealed class HttpPreProcessor
    {
        private readonly TcpClient client;
        private readonly WebServer webServer;

        private Stream inputStream;
        private StreamWriter outputWriter;

        /// <summary>
        /// http请求方法
        /// </summary>
        private RequestType requestType;

        /// <summary>
        /// http请求url
        /// </summary>
        private string requestUrl;

        /// <summary>
        /// http协议版本
        /// </summary>
        private string protocolVersion;

        /// <summary>
        /// header参数表
        /// </summary>
        private readonly Dictionary<string, string> httpHeaders = new Dictionary<string, string>();

        /// <summary>
        /// 请求参数
        /// </summary>
        private readonly Dictionary<string, string> requestParams = new Dictionary<string, string>();

        private const int MAX_POST_SIZE = 10 * 1024 * 1024;
        private const int POST_BUFFER_SIZE = 4096;

        /// <param name="client">Tcp客户端</param>
        /// <param name="webServer">请求处理实现对象</param>
        public HttpPreProcessor(TcpClient client, WebServer webServer)
        {
            this.client = client;
            this.webServer = webServer;
        }

        /// <summary>
        /// 处理http请求
        /// </summary>
        public void Process()
        {
            inputStream = new BufferedStream(client.GetStream());
            outputWriter = new StreamWriter(new BufferedStream(client.GetStream()));
            try
            {
                ParseHttpRequest();
                ParseHeaders();
                switch (requestType)
                {
                    case RequestType.Get:
                        HandleGETRequest();
                        break;
                    case RequestType.Post:
                        HandlePOSTRequest();
                        break;
                    default:
                        break;
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                StaticHandler.RequestNotFound(CreateRequestModel());
            }
            finally
            {
                if (outputWriter != null)
                {
                    outputWriter.Flush();
                    outputWriter.Close();
                    outputWriter = null;
                }

                if (inputStream != null)
                {
                    inputStream.Close();
                    inputStream = null;
                }

                client.Close();
            }
        }

        /// <summary>
        /// 解析http请求类型
        /// </summary>
        private void ParseHttpRequest()
        {
            string request = IOUtils.StreamReadLine(inputStream);
            string[] tokens = request.Split(' ');
            if (tokens.Length != 3)
            {
                throw new Exception("Invalid http request line!");
            }

            requestType = EnumUtils.GetRequestType(tokens[0]);
            requestUrl = tokens[1];
            protocolVersion = tokens[2];

            ConsoleUtils.WriteBlueLine($"\nReceive: {request}");
            ParseRequestUrl(requestUrl);
        }

        /// <summary>
        /// 解析请求地址和参数
        /// </summary>
        /// <param name="requestUrl">请求Url</param>
        private void ParseRequestUrl(string requestUrl)
        {
            string[] splited = requestUrl.Split("?");
            if (splited.Length <= 1)
            {
                this.requestUrl = requestUrl;
                requestParams.Clear();
            }
            else
            {
                this.requestUrl = splited[0];
                StringBuilder paramLine = new StringBuilder();
                for (int i = 1; i < splited.Length; i++)
                {
                    paramLine.Append(splited[i]);
                }

                string[] paramPairs = paramLine.ToString().Split("&");
                if (paramPairs.Length < 1)
                {
                    this.requestUrl = requestUrl;
                    requestParams.Clear();
                }
                else
                {
                    foreach (string paramPair in paramPairs)
                    {
                        string[] paramPairSplited = paramPair.Split("=");
                        if (paramPairSplited.Length != 2)
                        {
                            ConsoleUtils.WriteRedLine($"Error param: {paramPair}");
                            continue;
                        }

                        if (requestParams.ContainsKey(paramPairSplited[0]))
                        {
                            ConsoleUtils.WriteRedLine($"Same param key: {paramPairSplited[0]}");
                            continue;
                        }

                        requestParams.Add(paramPairSplited[0], paramPairSplited[1]);
                        ConsoleUtils.WriteTabLine($"{paramPairSplited[0]}: {paramPairSplited[1]}");
                    }
                }
            }

            ConsoleUtils.WriteBlueLine($"RequestUrl: {this.requestUrl}");
        }

        /// <summary>
        /// 解析http请求头
        /// </summary>
        public void ParseHeaders()
        {
            string line;
            while ((line = IOUtils.StreamReadLine(inputStream)) != null)
            {
                if (line.Equals("")) return;

                int separator = line.IndexOf(':');
                if (separator == -1)
                {
                    throw new Exception($"Invalid http header line: {line}");
                }

                string name = line.Substring(0, separator);
                int pos = separator + 1;
                while ((pos < line.Length) && (line[pos] == ' '))
                {
                    pos++;
                }

                string value = line.Substring(pos, line.Length - pos);
                ConsoleUtils.WriteTabLine($"Header: {name}:{value}");
                httpHeaders.Add(name, value);
            }
        }

        private void HandleGETRequest()
        {
            webServer.HandleGETRequest(CreateRequestModel());
        }

        private void HandlePOSTRequest()
        {
            MemoryStream ms = new MemoryStream();
            if (httpHeaders.ContainsKey("Content-Length"))
            {
                int contentLen = Convert.ToInt32(httpHeaders["Content-Length"]);
                if (contentLen > MAX_POST_SIZE)
                {
                    throw new Exception(
                        string.Format($"POST Content-Length({contentLen}) too big for this simple server!"));
                }

                byte[] buffer = new byte[POST_BUFFER_SIZE];
                int toRead = contentLen;
                while (toRead > 0)
                {
                    int readCount = inputStream.Read(buffer, 0, Math.Min(POST_BUFFER_SIZE, toRead));
                    if (readCount == 0)
                    {
                        if (toRead == 0)
                        {
                            break;
                        }
                        else
                        {
                            throw new Exception("Client disconnected during post!");
                        }
                    }

                    toRead -= readCount;
                    ms.Write(buffer, 0, readCount);
                }

                ms.Seek(0, SeekOrigin.Begin);
            }

            webServer.HandlerPOSTRequest(CreateRequestModel(), new StreamReader(ms));
        }

        /// <summary>
        /// 创建请求模型
        /// </summary>
        private RequestModel CreateRequestModel()
        {
            RequestModel model = new RequestModel();
            model.RequestType = requestType;
            model.RequestUrl = requestUrl;
            model.RequestParams = requestParams;
            model.ProtocolVersion = protocolVersion;
            model.HttpHeaders = httpHeaders;
            model.OutputWriter = outputWriter;
            return model;
        }
    }
}